﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace puzzle
{
    public class WarpField
    {
        public int Width { get; set; }
        public int Height { get; set; }

        private RectField field;
        private HashSet<Warper> warpers;
        private Dictionary<Box, Tuple<int,int>> colorSwitchers;
        private Dictionary<Box, int> blockerSwitches;
        private Dictionary<int, List<int>> slots;
        private List<List<Box>> globalPath;
        private List<Dictionary<Box, int>> sidePaths;
        private Random random;
        private Box firstBox;
        private Box lastBox;
        private HashSet<Box> colorSwitcherBoxPool;

        private static int nbPathsMaxFactor = 5;
        private static int emptyPartDivisor = 4; // >1

        public WarpField(int width, int height, int maxBoxSize, int minMeanBoxSize, int nbColors)
        {
            Width = width * 3;
            Height = height * 3;
            field = new RectField(width, height, maxBoxSize, minMeanBoxSize);
            random = new Random();
            warpers = new HashSet<Warper>();
            colorSwitchers = new Dictionary<Box, Tuple<int, int>>();
            blockerSwitches = new Dictionary<Box, int>();
            globalPath = new List<List<Box>>();
            sidePaths = new List<Dictionary<Box, int>>();

            slots = new Dictionary<int, List<int>>();
            foreach (Box box in field.getBoxes())
            {
                slots[box.ID] = new List<int>();
                for (int i = 0; i < box.getNbMaxSlots(); i++)
                {
                    slots[box.ID].Add(i);
                }
            }

            Dictionary<int,HashSet<Box>> colorBoxPool = new Dictionary<int,HashSet<Box>>();
            for (int i = 0; i < nbColors; i++)
            {
                colorBoxPool[i] = new HashSet<Box>(field.getBoxes());
            }
            colorSwitcherBoxPool = new HashSet<Box>(field.getBoxes());

            int currentColorID = random.Next(nbColors);
            HashSet<Box> boxPool = colorBoxPool[currentColorID];
            
            firstBox = getRandomBox(boxPool);
            boxPool.Remove(firstBox);

            addColorSwitch(firstBox, currentColorID);

            Box currentBox = firstBox;
            Box nextBox = null;

            int nbPathsMax = field.getBoxes().Count * nbPathsMaxFactor;
            
            for (int i = 0; i < nbPathsMax; i++)
            {
                nextBox = getRandomFreeBox(new HashSet<Box>(boxPool.Where(b=>colorSwitcherBoxPool.Contains(b))), 3);
                if (nextBox == null) break;

                boxPool.Remove(nextBox);

                createPath(random.Next(0, Math.Min(boxPool.Count, 5)), currentBox, nextBox, currentColorID, boxPool);


                bool breakLoop = true;
                for (int j = 0; j < nbColors; j++)
                {
                    if (colorBoxPool[j].Count >= Math.Max(2, field.getBoxes().Count / emptyPartDivisor) && !colorBoxPool[j].IsSubsetOf(colorSwitchers.Keys)) breakLoop = false;
                }
                if (breakLoop) break;


                int newColor;
                do{
                    newColor = random.Next(nbColors);
                } while (colorBoxPool[newColor].Count < Math.Max(2, field.getBoxes().Count / emptyPartDivisor) || colorBoxPool[newColor].IsSubsetOf(colorSwitchers.Keys)); //param

                if (currentColorID != newColor) addColorSwitch(nextBox, newColor);
                currentColorID = newColor;
                boxPool = colorBoxPool[currentColorID];
                

                currentBox = nextBox;
                boxPool.Remove(currentBox);

            }

            lastBox = nextBox;
            if (lastBox == null) lastBox = currentBox;

            bool ok = addBoxBlockers();

            if (ok)
            {
                foreach (Box box in field.getBoxes())
                {
                    if (box.Blocked == -1 && slots[box.ID].Count > 0) addBlockerSwitch(box);
                }
            } 
            else 
            {
                blockerSwitches.Clear();
                foreach (Box box in field.getBoxes())
                {
                    box.Blocked = -1;
                }
            }

        }

        public void checkBoxes(HashSet<Box> boxPool, int threshold)
        {
            HashSet<Box> tmp = new HashSet<Box>();
            foreach (Box box in boxPool)
            {
                if (!isBoxFree(box, threshold))
                {
                    tmp.Add(box);
                }
            }
            boxPool.RemoveWhere(b => tmp.Contains(b));
        }


        public void createPath(int length, Box box1, Box box2, int colorID, HashSet<Box> boxPool)
        {
            List<Box> path = new List<Box>();

            path.Add(box1);

            for (int i = 0; i < length - 1; i++)
            {
                Box box = getRandomFreeBox(boxPool, 3);

                path.Add(box);
                boxPool.Remove(box);
            }
            path.Add(box2);

            for (int j = 0; j < path.Count - 1; j++)
            {
                addWarper(path[j], path[j + 1], colorID);
            }

            //sideboxes
            HashSet<Box> sideBoxes = new HashSet<Box>();
            Dictionary<Box, int> connectedBoxes = new Dictionary<Box, int>();
            if (boxPool.Count >= 1)
            {
                int nbBoxes = Math.Min(boxPool.Count, random.Next(5));
            
                for (int i = 0; i < nbBoxes; i++)
                {
                    Box box = getRandomFreeBox(boxPool, 2);
                    if (box != null)
                    {
                        sideBoxes.Add(box);
                        boxPool.Remove(box);
                    }
                }
                foreach (Box box in sideBoxes)
                {
                    bool fullPath = true;
                    foreach (Box b in path)
                    {
                        if (isBoxFree(b, 3))
                        {
                            fullPath = false;
                            break;
                        }
                    }

                    if (!fullPath)
                    {
                        Box b1;
                        int index;
                        do
                        {
                            index = random.Next(path.Count);
                            b1 = path[index];
                        } while (!isBoxFree(b1, 3));

                        addWarper(box, b1, colorID);
                        connectedBoxes.Add(box,index);
                    }
                    else
                    {
                        HashSet<Box> tmp = new HashSet<Box>(connectedBoxes.Keys);
                        Box b1 = getRandomFreeBox(tmp, 1);
                        if (b1 != null)
                        {
                            addWarper(box, b1, colorID);
                            connectedBoxes.Add(box, connectedBoxes[b1]);
                        }
                    }
                }

                foreach (Box box in sideBoxes)
                {
                    if (connectedBoxes.ContainsKey(box))
                    {
                        if (slots[box.ID].Count > 0)
                        {
                            Box b2 = getRandomFreeBox(new HashSet<Box>(connectedBoxes.Keys.Where(b => b != box)), 2);
                            if (b2 != null) addWarper(box, b2, colorID);
                        }
                    }
                    else
                    {
                        boxPool.Add(box);
                    }
                }
            }

            globalPath.Add(path);
            sidePaths.Add(connectedBoxes);

        }

        public bool addBoxBlockers()
        {
            List<Box> path = new List<Box>();
            path.Add(firstBox);
            for (int i = 0; i < globalPath.Count; i++)
            {
                for (int j = 1; j < globalPath[i].Count; j++)
                {
                    path.Add(globalPath[i][j]);
                }
            }

            int tries = 0;
            do
            {
                firstBox.Blocked = 0;

                int lastState = 0;

                for (int i = 1; i < path.Count; i++)
                {
                    path[i].Blocked = -1;
                    if (slots[path[i].ID].Count == 0)
                    {
                        bool b1 = true;
                        bool b2 = true;
                        for (int j = 1; j < i; j++)
                        {
                            if (path[j] == path[i])
                            {
                                if (path[j + 1].Blocked == 0 || path[j - 1].Blocked == 0) b2 = false;
                                if (path[j + 1].Blocked == 1 || path[j - 1].Blocked == 1) b1 = false;
                            }
                        }
                        if (i < path.Count - 1)
                        {
                            if (path[i - 1].Blocked == 0 || path[i + 1].Blocked == 0) b2 = false;
                            if (path[i - 1].Blocked == 1 || path[i + 1].Blocked == 1) b1 = false;
                        }
                        else
                        {
                            if (path[i - 1].Blocked == 0) b2 = false;
                            if (path[i - 1].Blocked == 1) b1 = false;
                        }

                        if (b1 && b2)
                        {
                            if (lastState == 0)
                            {
                                path[i].Blocked = 1;
                                lastState = 1;
                            }
                            else if (lastState == 1)
                            {
                                path[i].Blocked = 0;
                                lastState = 0;
                            }
                        }
                        else if (b1) path[i].Blocked = 0;
                        else if (b2) path[i].Blocked = 1;
                        else return false;
                    }
                }

                lastState = 0;
                for (int i = 1; i < path.Count; i++)
                {
                    if (path[i].Blocked == -1)
                    {
                        bool b1 = true;
                        bool b2 = true;
                        for (int j = 1; j < i; j++)
                        {
                            if (path[j] == path[i])
                            {
                                if (path[j + 1].Blocked == 0 || path[j - 1].Blocked == 0) b2 = false;
                                if (path[j + 1].Blocked == 1 || path[j - 1].Blocked == 1) b1 = false;
                            }
                        }
                        if (i < path.Count - 1)
                        {
                            if (path[i - 1].Blocked == 0 || path[i + 1].Blocked == 0) b2 = false;
                            if (path[i - 1].Blocked == 1 || path[i + 1].Blocked == 1) b1 = false;
                        }
                        else
                        {
                            if (path[i - 1].Blocked == 0) b2 = false;
                            if (path[i - 1].Blocked == 1) b1 = false;
                        }

                        if (b1 && b2)
                        {
                            if (lastState == 0)
                            {
                                path[i].Blocked = 1;
                                lastState = 1;
                            }
                            else if (lastState == 1)
                            {
                                path[i].Blocked = 0;
                                lastState = 0;
                            }
                        }
                        else if (b1) path[i].Blocked = random.Next(2) - 1;
                        else if (b2) path[i].Blocked = (random.Next(2) * 2) - 1;
                    }
                }
                tries++;

            } while (!isDoable() && tries < 100);

            return isDoable();
        }

        public bool isDoable()
        {
            int lastBoxState = firstBox.Blocked;
            int currentBoxState;
            for (int i = 0; i < globalPath.Count; i++)
            {
                for (int j = 1; j < globalPath[i].Count; j++)
                {
                    currentBoxState = globalPath[i][j].Blocked;
                    if ((lastBoxState == 0 && currentBoxState == 1) || (lastBoxState == 0 && currentBoxState == 1))
                    {
                        return false;
                    }
                    lastBoxState = currentBoxState;
                }
            }

            return true;
        }

        public void addBlockerSwitch(Box box)
        {
            if (!blockerSwitches.ContainsKey(box))
            {
                int slot = slots[box.ID][random.Next(slots[box.ID].Count)];
                blockerSwitches.Add(box, slot);
                slots[box.ID].Remove(slot);
            }
        }

        public void addWarper(Box b1, Box b2, int colorID){
            Warper warper = new Warper(b1, b2, colorID);
            if (!warpers.Contains(warper) && b1 != b2)
            {
                warpers.Add(warper);

                int slot1 = slots[b1.ID][random.Next(slots[b1.ID].Count)];
                slots[b1.ID].Remove(slot1);
                warper.Slot1 = slot1;

                int slot2 = slots[b2.ID][random.Next(slots[b2.ID].Count)];
                slots[b2.ID].Remove(slot2);
                warper.Slot2 = slot2;
            }
        }

        public void addColorSwitch(Box box, int colorID)
        {
            int slot = slots[box.ID][random.Next(slots[box.ID].Count)];
            colorSwitchers.Add(box, new Tuple<int,int>(slot, colorID));
            slots[box.ID].Remove(slot);
            colorSwitcherBoxPool.Remove(box);
        }

        public HashSet<Box> getBoxes(){
            return field.getBoxes();
        }

        public HashSet<Warper> getWarpers(){
            return warpers;
        }

        public Dictionary<Box, Tuple<int,int>> getColorSwitchers()
        {
            return colorSwitchers;
        }

        public Dictionary<Box, int> getBlockerSwitches()
        {
            return blockerSwitches;
        }

        public Box getFirstBox()
        {
            return firstBox;
        }

        public Box getLastBox()
        {
            return lastBox;
        }

        public bool isBoxFree(Box box, int threshold){
            return slots[box.ID].Count >= threshold;
        }

        public Box getRandomFreeBox(HashSet<Box> set, int threshold){
            Box[] array = set.Where(b => isBoxFree(b, threshold)).ToArray();
            Box box = null;
            if (array.Length > 0)
            {
                int index = random.Next(array.Length);
                box = array[index];
            }

            return box;
        }

        public Box getRandomBox(HashSet<Box> set)
        {
            Box[] array = set.ToArray();
            Box box = null;
            if (array.Length > 0)
            {
                int index = random.Next(array.Length);
                box = array[index];
            }

            return box;
        }
    }
}
